
#include <iostream>
#include <stdlib.h>
#include <malloc.h>
#include <cmath>
#include <math.h>
//Includes GL
#include <GL/glew.h>
//Includes CUDA
#include <cuda_runtime.h>
#include <cutil.h>
#include "Init.h"

using namespace std;

bool verificaParametros(int argc, char** argv) {
    //Verifica se todos os parametros realmente foram passados na chamada do programa.
    if (argc < 5) {
        cout << "Falta parametros!" << endl;
        return false;
    }

    resolucaoPartX = atof(argv[1]);
    resolucaoPartY = atof(argv[2]);
    resolucaoGridX = atof(argv[3]);
    resolucaoGridY = atof(argv[4]);

    return true;
}

void init(){
    iniciaDados();
    iniciaEstruturas();
    iniciaParticulas();
    posicionaParticulas();
    iniciaRender();
    iniciaCuda();
}

void iniciaRender(void) {
    // Define a cor de fundo da janela de visualização como preta
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glPointSize(3.0);
    //    glClearDepth(1.0);
    //    glEnable(GL_LIGHT0);
    //    glEnable(GL_LIGHTING);
    //    glEnable(GL_DEPTH_TEST);
}

void iniciaDados() {

    XminGrid = 0.0;
    XmaxGrid = 1.0;
    YminGrid = 0.0;
    YmaxGrid = 1.0;

    mostraGrid = true;
    mostraEixo = false;
    mostraVetores = true;
    mostraPressao = true;
    mostraViscosidade = true;
    mostraAceleracao = true;

    totalParticulas = resolucaoPartX * resolucaoPartY;
    cout << "Total de particulas: " << totalParticulas << endl;
    deltaXGrid = (XmaxGrid - XminGrid) / resolucaoGridX;
    deltaYGrid = (YmaxGrid - YminGrid) / resolucaoGridY;
    totalCelulasGrid = resolucaoGridX * resolucaoGridY;
    deltaXPart = (((XmaxGrid - (25 * (XmaxGrid - XminGrid)) / 100) - (XminGrid + (25 * (XmaxGrid - XminGrid)) / 100)) / resolucaoPartX);
    deltaYPart = (((YmaxGrid - (25 * (YmaxGrid - YminGrid)) / 100) - (YminGrid + (25 * (YmaxGrid - YminGrid)) / 100)) / resolucaoPartY);
    posicaoX = XminGrid + ((25 * (XmaxGrid - XminGrid)) /100);
    posicaoY = YminGrid + ((25 * (YmaxGrid - YminGrid)) / 100);

    threadsPerBlock = 512;
    blocksPerGrid = (totalParticulas + threadsPerBlock - 1) / threadsPerBlock;

    massa = 0.5 / totalParticulas;
    cout << "Massa = " << massa << "g." << endl;
    massaEspecificaInicial = (0.5 / 0.25);
    cout << "Massa especifica inicial = " << massaEspecificaInicial << "g." << endl;

    count = (unsigned int *) malloc(totalCelulasGrid * sizeof (unsigned int));
    offSet = (unsigned int *) malloc(totalCelulasGrid * sizeof (unsigned int));


}

void iniciaEstruturas() {
    posicoesParticulas = (float3 *) malloc(totalParticulas * sizeof (float3));
    posicoesParticulas2 = (float3 *) malloc(totalParticulas * sizeof (float3));
    forcaPressaoVetor = (float3 *) malloc(totalParticulas * sizeof (float3));
    forcaAceleracaoVetor = (float3 *) malloc(totalParticulas * sizeof (float3));
    forcaViscosidadeVetor = (float3 *) malloc(totalParticulas * sizeof (float3));
    particulas = (Particula *) malloc(totalParticulas * sizeof (Particula));
    particulas2 = (Particula *) malloc(totalParticulas * sizeof (Particula));
    keys = (unsigned int *) malloc(totalParticulas * sizeof (unsigned int));
    values = (unsigned int *) malloc(totalParticulas * sizeof (unsigned int));

    posicoesParticulasT = posicoesParticulas;
    posicoesParticulasT2 = posicoesParticulas2;
    particulasT = particulas;
    particulasT2 = particulas2;
}

void iniciaParticulas() {
    for (int i = 0; i < totalParticulas; i++) {
        particulas[i].massaEspecifica = massaEspecificaInicial;
        particulas[i].pressao = 1;
        particulas[i].velocidade.x = 0;
        particulas[i].velocidade.y = 0;
        particulas[i].velocidade.z = 0;
        particulas2[i].massaEspecifica = massaEspecificaInicial;
        particulas2[i].pressao = 1;
        particulas2[i].velocidade.x = 0;
        particulas2[i].velocidade.y = 0;
        particulas2[i].velocidade.z = 0;
        values[i] = i; //Gera os values.
        forcaPressaoVetor[i].x = forcaPressaoVetor[i].y = forcaPressaoVetor[i].z = 0;
        forcaAceleracaoVetor[i].x = forcaAceleracaoVetor[i].y = forcaAceleracaoVetor[i].z = 0;
        forcaViscosidadeVetor[i].x = forcaViscosidadeVetor[i].y = forcaViscosidadeVetor[i].z = 0;
    }
}

void posicionaParticulas() {

    //PREENCHENDO AS POSIÇÕES X.
    int contElementosLinha = 0;
    for (int j = 0; j < totalParticulas; j++) {
        posicoesParticulas[j].x = posicaoX;
        posicoesParticulas2[j].x = posicaoX;

        contElementosLinha++;
        posicaoX = posicaoX + deltaXPart;
        if (contElementosLinha >= resolucaoPartX) {
            posicaoX = XminGrid + ((25 * (XmaxGrid - XminGrid)) /100);
            contElementosLinha = 0;
        }
    }

    //PREENCHENDO AS POSIÇÕES Y.
    int contElementosColuna = 0;
    for (int j = 0; j < totalParticulas; j++) {
        posicoesParticulas[j].y = posicaoY;
        posicoesParticulas2[j].y = posicaoY;
        contElementosColuna++;
        if (contElementosColuna >= resolucaoPartX) {
            posicaoY = posicaoY + deltaYPart;
            contElementosColuna = 0;
        }
    }

    //PREENCHENDO AS POSIÇÕES Z.
    for (int j = 0; j < totalParticulas; j++) {
            posicoesParticulas[j].z = 0.0;
            posicoesParticulas2[j].z = 0.0;
    }
}

void iniciaCuda() {
    CUDA_SAFE_CALL(cudaMalloc((void **) & particulasDevice, totalParticulas * sizeof (Particula)));
    CUDA_SAFE_CALL(cudaMalloc((void **) & particulasDevice2, totalParticulas * sizeof (Particula)));
    CUDA_SAFE_CALL(cudaMalloc((void **) & keysDevice, totalParticulas * sizeof (unsigned int)));
    CUDA_SAFE_CALL(cudaMalloc((void **) & valuesDevice, totalParticulas * sizeof (unsigned int)));
    CUDA_SAFE_CALL(cudaMalloc((void **) & countDevice, totalParticulas * sizeof (unsigned int)));
    CUDA_SAFE_CALL(cudaMalloc((void **) & offSetDevice, totalParticulas * sizeof (unsigned int)));
    CUDA_SAFE_CALL(cudaMemcpy(valuesDevice, values, totalParticulas * sizeof (unsigned int), cudaMemcpyHostToDevice));
    CUDA_SAFE_CALL(cudaMemcpy(particulasDevice, particulas, totalParticulas * sizeof (Particula), cudaMemcpyHostToDevice));
    CUDA_SAFE_CALL(cudaMemcpy(particulasDevice2, particulas2, totalParticulas * sizeof (Particula), cudaMemcpyHostToDevice));
    CUDA_SAFE_CALL(cudaMemcpy(countDevice, count, totalParticulas * sizeof (unsigned int), cudaMemcpyHostToDevice));
}
